package http.secure.common.keys.validator.impl;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import http.secure.common.constant.SecureProperties;
import http.secure.common.keys.validator.RepeatValidator;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.codec.binary.HexUtil;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.TimeUnit;

/**
 * 高性能重复请求验证器 - 基于内存映射文件和Caffeine缓存
 * 仅用于单机环境/小型项目测试使用
 */
@Slf4j
public class MappedRepeatValidator implements RepeatValidator, InitializingBean, DisposableBean {
    /**
     * 请求数据块大小 (8字节时间戳 + 16字节签名哈希)
     */
    private static final int BLOCK_SIZE = 24;

    /**
     * 内存缓存
     */
    private final Cache<String, Long> cache;

    /**
     * 磁盘刷新通知锁
     */
    private final Object flushLock = new Object();

    /**
     * 写入操作锁
     */
    private final Object writeLock = new Object();

    /**
     * 缓存文件
     */
    private final File cacheFile;

    /**
     * 链接有效时间(毫秒)
     */
    private final int effectiveTime;

    /**
     * 每秒最大并发数
     */
    private final int maxConcurrencyPerSecond;

    /**
     * 内存映射缓冲区
     */
    private MappedByteBuffer buffer;

    /**
     * 文件同步线程
     */
    private Thread flushThread;

    /**
     * 是否已关闭
     */
    private volatile boolean shutdown = false;

    /**
     * 构造函数
     *
     * @param cacheFile  缓存文件
     * @param properties 安全配置属性
     */
    public MappedRepeatValidator(File cacheFile, SecureProperties properties) {
        validateParameters(properties);

        this.cacheFile = cacheFile;
        this.effectiveTime = properties.getLinkValidTime();
        this.maxConcurrencyPerSecond = properties.getMaxConcurrencyPerSecond();
        // 计算最大条目数
        int maxEntries = (int) (Math.ceil(effectiveTime / 1000.0) * maxConcurrencyPerSecond);
        long maxFileSize = (long) maxEntries * BLOCK_SIZE;
        if (maxFileSize > Integer.MAX_VALUE) {
            throw new IllegalArgumentException("Calculated file size exceeds maximum allowed (2GB)");
        }
        // 初始化Caffeine缓存
        this.cache = Caffeine.newBuilder()
                .expireAfterWrite(effectiveTime, TimeUnit.MILLISECONDS)
                .maximumSize(maxEntries)
                .removalListener((key, value, cause) -> {
                    if (cause.wasEvicted()) {
                        log.debug("Entry evicted from cache: {} (cause: {})", key, cause);
                    }
                })
                .build();
    }

    /**
     * 验证构造参数
     */
    private void validateParameters(SecureProperties properties) {
        if (properties.getLinkValidTime() <= 0) {
            throw new IllegalArgumentException("Link valid time must be positive");
        }
        if (properties.getMaxConcurrencyPerSecond() <= 0) {
            throw new IllegalArgumentException("Maximum concurrency per second must be positive");
        }
    }

    @Override
    public boolean isRepeated(long timestamp, String signature) {
        // 检查是否已在缓存中
        if (cache.getIfPresent(signature) != null) {
            log.debug("Duplicate request detected: {}", signature);
            return true;
        }

        // 检查是否达到容量上限
        if (cache.estimatedSize() >= cache.policy().eviction().get().getMaximum()) {
            log.warn("Cache capacity reached, rejecting request: {}", signature);
            return true;
        }

        // 添加到缓存
        cache.put(signature, timestamp);

        // 尝试写入文件
        try {
            writeToFile(timestamp, signature);
            return false;
        } catch (Exception e) {
            log.error("Failed to write to buffer, falling back to memory-only mode", e);
            // 文件写入失败，但请求仍有效
            return false;
        }
    }

    /**
     * 写入数据到内存映射文件
     */
    private void writeToFile(long timestamp, String signature) throws IOException {
        synchronized (writeLock) {
            ensureCapacity();
            // 写入时间戳和签名
            buffer.putLong(timestamp);
            buffer.put(HexUtil.decode(signature));
            // 触发异步刷新
            triggerFlush();
        }
    }

    /**
     * 确保缓冲区有足够的空间
     */
    private void ensureCapacity() throws IOException {
        if (buffer.remaining() < BLOCK_SIZE) {
            // 先尝试清理过期数据
            clearExpiredData();
            // 如果仍然没有足够空间，考虑扩容
            if (buffer.remaining() < BLOCK_SIZE) {
                expandBuffer();
            }
        }
    }

    /**
     * 清理过期数据
     */
    private void clearExpiredData() {
        long currentTime = System.currentTimeMillis();
        int validEntries = 0;
        synchronized (writeLock) {
            // 1. 保存当前数据
            byte[] allData = new byte[buffer.position()];
            buffer.position(0);
            buffer.get(allData);
            // 2. 重置缓冲区
            buffer.clear();
            // 3. 只复制未过期的数据
            for (int i = 0; i < allData.length / BLOCK_SIZE; i++) {
                int offset = i * BLOCK_SIZE;
                long timestamp = ByteBuffer.wrap(allData, offset, 8).getLong();
                if (currentTime - timestamp <= effectiveTime) {
                    // 复制未过期的数据
                    System.arraycopy(allData, offset, allData, validEntries * BLOCK_SIZE, BLOCK_SIZE);
                    validEntries++;
                }
            }
            // 4. 写回有效数据
            buffer.put(allData, 0, validEntries * BLOCK_SIZE);
            buffer.position(validEntries * BLOCK_SIZE);
        }
        log.debug("Cleared expired data, {} valid entries remain", validEntries);
    }

    /**
     * 扩展缓冲区容量
     */
    private void expandBuffer() throws IOException {
        long currentSize = buffer.capacity();
        long maxSize = (long) Math.ceil(effectiveTime / 1000.0) * maxConcurrencyPerSecond * BLOCK_SIZE;
        long newSize = Math.min(currentSize * 2, maxSize);
        if (newSize == currentSize) {
            throw new IllegalStateException("Buffer has reached maximum size, cannot expand further");
        }
        log.info("Expanding buffer from {} to {} bytes", currentSize, newSize);
        // 创建新缓冲区
        MappedByteBuffer newBuffer = initBuffer(newSize);
        // 复制现有数据
        buffer.flip();
        newBuffer.put(buffer);
        // 切换到新缓冲区
        this.buffer = newBuffer;
    }

    /**
     * 触发文件刷新
     */
    private void triggerFlush() {
        synchronized (flushLock) {
            flushLock.notify();
        }
    }

    /**
     * 初始化内存映射缓冲区
     */
    private MappedByteBuffer initBuffer(long size) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(cacheFile, "rw")) {
            try (FileChannel channel = raf.getChannel()) {
                return channel.map(FileChannel.MapMode.READ_WRITE, 0, size);
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        initializeBuffer();
        startFlushThread();
    }

    /**
     * 初始化缓冲区
     */
    private void initializeBuffer() throws IOException {
        // 确保文件存在
        if (!cacheFile.exists()) {
            if (!cacheFile.createNewFile()) {
                throw new IOException("Failed to create cache file: " + cacheFile.getAbsolutePath());
            }
        }
        // 计算最大文件大小
        long maxSize = (long) Math.ceil(effectiveTime / 1000.0) * maxConcurrencyPerSecond * BLOCK_SIZE;
        if (maxSize > Integer.MAX_VALUE) {
            maxSize = Integer.MAX_VALUE;
        }
        // 初始化或调整文件大小
        if (cacheFile.length() == 0) {
            try (RandomAccessFile raf = new RandomAccessFile(cacheFile, "rw")) {
                raf.setLength(maxSize);
            }
        }
        this.buffer = initBuffer(maxSize);
        // 从文件加载数据
        loadFromFile();
    }

    /**
     * 从文件加载数据到缓存
     */
    private void loadFromFile() {
        long currentTime = System.currentTimeMillis();
        int validEntries = 0;
        try {
            synchronized (writeLock) {
                while (buffer.hasRemaining()) {
                    int position = buffer.position();
                    long timestamp;
                    try {
                        timestamp = buffer.getLong();
                    } catch (BufferUnderflowException e) {
                        // 数据不完整，回退位置
                        buffer.position(position);
                        break;
                    }
                    // 检查是否为有效时间戳
                    if (timestamp <= 0 || timestamp > currentTime + effectiveTime) {
                        buffer.position(position);
                        break;
                    }
                    // 检查是否过期
                    if (currentTime - timestamp <= effectiveTime) {
                        byte[] signBytes = new byte[16];
                        if (buffer.remaining() < 16) {
                            buffer.position(position);
                            break;
                        }
                        buffer.get(signBytes);
                        cache.put(HexUtil.encodeStr(signBytes), timestamp);
                        validEntries++;
                    } else {
                        // 跳过过期数据
                        if (buffer.remaining() < 16) {
                            buffer.position(position);
                            break;
                        }
                        buffer.position(buffer.position() + 16);
                    }
                }
                // 重置写入位置
                buffer.position(validEntries * BLOCK_SIZE);
            }
            log.info("Loaded {} valid entries from file", validEntries);
        } catch (Exception e) {
            log.error("Error loading data from file", e);
            // 出错时重置缓冲区
            try {
                this.buffer = initBuffer((long) Math.ceil(effectiveTime / 1000.0) *
                        maxConcurrencyPerSecond * BLOCK_SIZE);
            } catch (IOException ex) {
                log.error("Failed to reset buffer after load error", ex);
            }
        }
    }

    /**
     * 启动文件刷新线程
     */
    private void startFlushThread() {
        this.flushThread = new Thread(this::flushLoop, "RepeatValidator-FlushThread");
        this.flushThread.setDaemon(true);
        this.flushThread.start();
        log.info("File flush thread started");
    }

    /**
     * 文件刷新循环
     */
    private void flushLoop() {
        while (!shutdown) {
            synchronized (flushLock) {
                try {
                    // 每100ms检查一次，或有数据时立即处理
                    flushLock.wait(100);
                    if (shutdown) {
                        break;
                    }
                    // 执行刷新
                    synchronized (writeLock) {
                        if (buffer != null && buffer.position() > 0) {
                            buffer.force();
                            log.debug("Flushed {} bytes to disk", buffer.position());
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } catch (Exception e) {
                    log.error("Error during file flush", e);
                }
            }
        }
        // 关闭前的最终刷新
        try {
            synchronized (writeLock) {
                if (buffer != null) {
                    buffer.force();
                    log.info("Final flush completed");
                }
            }
        } catch (Exception e) {
            log.error("Error during final flush", e);
        }
    }

    @Override
    public void destroy() {
        shutdown = true;
        // 唤醒刷新线程
        synchronized (flushLock) {
            flushLock.notify();
        }
        // 等待刷新线程结束
        if (flushThread != null && Thread.currentThread() != flushThread) {
            try {
                flushThread.join(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        log.info("MappedRepeatValidator shutdown completed");
    }
}
